﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genuilder.Extensibility;
using Genuilder.Extensibility.NRefactory;
using System.Runtime.Serialization;
using ICSharpCode.NRefactory.Visitors;
using System.ServiceModel;
using ICSharpCode.NRefactory.Ast;

namespace Genuilder.Extensions
{
	[DataContract]
	public class AsyncServiceContractsExtension : IExtension
	{
		class AsyncVisitor : AbstractAstVisitor
		{
			private CodeWriter writer;

			public AsyncVisitor(CodeWriter writer)
			{
				this.writer = writer;
			}

			public override object VisitUsing(ICSharpCode.NRefactory.Ast.Using @using, object data)
			{
				writer.WriteUsing(@using.Name);
				return base.VisitUsing(@using, data);
			}

			public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
			{
				using(writer.WriteNamespace(namespaceDeclaration.Name))
				{
					return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				}
			}

			public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
			{
				if(typeDeclaration.Type == ICSharpCode.NRefactory.Ast.ClassType.Interface)
				{
					writer.WriteAttribute<ServiceContractAttribute>()
						.WithProperty(o => o.Name, "\"" + typeDeclaration.Name + "\"")
						.WriteProperties(GetAttributeDeclarations(typeDeclaration, "ServiceContract"));
					writer.Flush();
					var knownTypes = typeDeclaration.Attributes.SelectMany(a => a.Attributes).Where(a => a.Name.Contains("ServiceKnownType"));
					foreach(var knownType in knownTypes)
					{
						writer.Write("[" + knownType.ToCs() + "]");
						writer.NewLine();
					}
					writer.Write("public interface " + typeDeclaration.Name + "Async");
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						return base.VisitTypeDeclaration(typeDeclaration, data);
					}
				}
				return null;
			}

			public override object VisitMethodDeclaration(ICSharpCode.NRefactory.Ast.MethodDeclaration methodDeclaration, object data)
			{
				writer.WriteAttribute<OperationContractAttribute>()
						.WithProperty(o => o.Name, "\"" + methodDeclaration.Name + "\"")
						.WriteProperties(GetAttributeDeclarations(methodDeclaration, "OperationContract"))
						.WithProperty(o => o.AsyncPattern, "true");
				writer.NewLine();
				writer.Flush();
				writer.Write("System.IAsyncResult Begin" + methodDeclaration.Name);
				writer.WriteParameterDeclarations(methodDeclaration.Parameters.Select(p => new ParameterDeclaration(p.TypeReference.GetNameOrVoid(), p.ParameterName)).Add(new ParameterDeclaration("System.AsyncCallback ac"), new ParameterDeclaration("object state")).ToArray());
				writer.Write(";");
				writer.NewLine();
				writer.Write(methodDeclaration.TypeReference.GetNameOrVoid() + " End" + methodDeclaration.Name + "(System.IAsyncResult ar);");
				writer.NewLine();
				return null;
			}

			private IEnumerable<AttributePropertyDeclaration> GetAttributeDeclarations(ICSharpCode.NRefactory.Ast.AttributedNode node, string attributeName)
			{
				return node.Attributes
						.SelectMany(a => a.Attributes)
						.Where(a => a.Name.Contains(attributeName))
						.SelectMany(a => a.NamedArguments)
						.Select(na => new AttributePropertyDeclaration(na.Name, na.Expression.ToCs()));
			}
		}

		#region IExtension Members

		[DataMember]
		public String[] Files
		{
			get;
			set;
		}

		[DataMember]
		public FileQuery FileQuery
		{
			get;
			set;
		}

		public void Execute(ExtensionContext extensionContext)
		{
			var fileQuery = FileQuery ?? new FileQuery();
			fileQuery.SelectByNames(Files);
			foreach(var item in extensionContext.GenItems.GetByQuery(fileQuery).Where(i => i.Modified))
			{
				var asyncItem = item.Children.CreateNew("Async" + item.File.Name);
				using(var fs = asyncItem.Open())
				{
					CodeWriter writer = new CodeWriter(fs);
					var itemAst = item.GetExtension<CompilationUnitExtension>();
					itemAst.ParseMethodBodies = false;
					itemAst.CompilationUnit.AcceptVisitor(new AsyncVisitor(writer), null);
					writer.Flush();
				}
			}
		}

		#endregion
	}
}
